﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace Tcp.StudyServer
{
    /// <summary>
    /// ServerByTcpListener.xaml 的交互逻辑
    /// </summary>
    public partial class ServerByTcpListener : Window
    {
        // 服务器监听对象
        TcpListener server;

        #region 已连接的客户端集合
        private ObservableCollection<Socket> _SocketList = new();

        public ObservableCollection<Socket> SocketList
        {
            get { return _SocketList; }
            set { _SocketList = value; }
        }
        #endregion

        public ServerByTcpListener()
        {
            InitializeComponent();

            DataContext = this; // 上下文
        }

        /// <summary>
        /// 打开(开启监听)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnOpen_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                #region socket实现
                // SocketType.Stream 可靠 双向 基于connect
                //server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //string ip = txtIP.Text;
                //server.Bind(new IPEndPoint(IPAddress.Parse(ip), int.Parse(txtPort.Text)));
                //server.Listen(5);  // 监听 参数backlog是等着排队队列的最大值

                //// 监听客户端连接(同步)，并获取客户端对象,会阻塞(一直等哭护短连接)，这里只是打开服务器(不写这里)
                ////Socket client = server.Accept();

                //AppendTxt("服务器打开成功", Colors.Green);
                //BtnOpen.Visibility = Visibility.Collapsed;
                //BtnClose.Visibility = Visibility.Visible;
                #endregion

                #region TcpListener实现
                string ip = txtIP.Text;
                int port = Convert.ToInt32(txtPort.Text);
                server = new TcpListener(IPAddress.Parse(ip), port);
                // 监听
                server.Start();

                AppendTxt("服务器打开成功", Colors.Green);
                BtnOpen.Visibility = Visibility.Collapsed;
                BtnClose.Visibility = Visibility.Visible;
                #endregion


                // 不断接收新的客户端连接并绑定到下拉并接收客户端的消息
                Task.Run(async () =>
                {
                    while (true)
                    {
                        if (server != null)
                        {
                            Socket client = await server.AcceptSocketAsync();
                            Dispatcher.Invoke(() =>
                            {
                                SocketList.Add(client); // 添加到客户端列表
                                AppendTxt($"{client.RemoteEndPoint}已连接", Colors.Orange);
                            });

                            // 接收客户端消息
                            ReceiveMsg(client);

                        }
                        await Task.Delay(1000);
                    }
                });

                // 检测客户端连接，如果客户端断开则移除
                Task.Run(async () =>
                {
                    while (true)
                    {
                        Dispatcher.Invoke(() =>
                        {
                            for (int i = 0; i < SocketList.Count; i++)
                            {
                                Socket client = SocketList[i];

                                // 服务器端主动关闭
                                if (!client.Connected)
                                {
                                    SocketList.Remove(client);
                                    i--;
                                    continue;
                                }

                                // 客户端断开，服务器马上知道
                                // 判断套接字读状态是否有数据可读,且接收缓冲区没有可读取的字节
                                if (client.Poll(1000, SelectMode.SelectRead) && client.Available == 0)
                                {
                                    SocketList.Remove(client);
                                    i--;
                                    AppendTxt($"客户端{client.RemoteEndPoint},已断开", Colors.Red);
                                }

                            }
                        });

                        await Task.Delay(1000);
                    }
                });
            }
            catch (Exception ex)
            {
                AppendTxt($"服务器打开异常,{ex.Message}", Colors.Red);
                BtnOpen.Visibility = Visibility.Visible;
                BtnClose.Visibility = Visibility.Collapsed;
            }
        }

        /// <summary>
        /// 关闭
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnClose_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                if (server != null)
                {
                    // 这时就有一个暗含需求,断开所有客户端
                    foreach (var client in SocketList)
                    {
                        if (client.Connected)
                        {
                            client.Close();
                        }
                    }

                    // 问题:服务器关闭，会在合适的时间才断开连接.所以其实客户端还是连接着且能发消息
                    server.Stop();

                    AppendTxt($"服务器已关闭", Colors.Green);

                    BtnOpen.Visibility = Visibility.Visible;
                    BtnClose.Visibility = Visibility.Collapsed;
                }
            }
            catch (Exception ex)
            {
                AppendTxt($"服务器失败,{ex.Message}", Colors.Red);
            }
        }

        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void BtnSend_Click(object sender, RoutedEventArgs e)
        {
            Socket? selectedClient = cbbClient.SelectedItem as Socket;
            if (server != null && selectedClient != null)
            {
                if (selectedClient.Connected)
                {
                    string msg = txtSendContent.Text;
                    byte[] sendBytes = Encoding.UTF8.GetBytes(msg);
                    int sendResult = await selectedClient.SendAsync(sendBytes); // 返回值 发送成功的字节数
                    if (sendResult > 0)
                    {
                        AppendTxt($"发送消息:{msg}", Colors.Black);
                    }
                }
            }
        }

        /// <summary>
        /// 不停接收指定客户端的消息
        /// </summary>
        /// <param name="client"></param>
        private void ReceiveMsg(Socket client)
        {
            Task.Run(async () =>
            {
                while (true)
                {
                    _ = Dispatcher.Invoke(async () =>
                    {
                        //byte[] recBytes = new byte[1024];// 接收的结果
                        byte[] recBytes = new byte[client.Available];
                        int recBytesCount = await client.ReceiveAsync(recBytes); // 接收的字节数量

                        if (recBytesCount > 0) // 判断是否有数据
                        {
                            string recStr = Encoding.UTF8.GetString(recBytes);

                            AppendTxt($"来自{client.RemoteEndPoint},的消息:{recStr}", Colors.Blue);
                        }
                    });

                    await Task.Delay(1000);
                }
            });
        }


        FlowDocument doc = new FlowDocument();
        /// <summary>
        /// 追加信息到richtextbox结尾
        /// </summary>
        /// <param name="txt"></param>
        /// <param name="txtColor">文字颜色</param>
        private void AppendTxt(string txt, Color txtColor)
        {
            var p = new Paragraph(); // Paragraph 类似于 html 的 P 标签
            var r = new Run($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} {txt}"); // Run 是一个 Inline 的标签
            p.Inlines.Add(r);
            p.Foreground = new SolidColorBrush(txtColor);//设置字体颜色
            doc.Blocks.Add(p);
            txtShowContent.Document = doc;
            txtShowContent.ScrollToEnd();
        }

    }
}
